package com.shm.leetcode;

/**
 * 839. 相似字符串组
 * 如果交换字符串 X 中的两个不同位置的字母，使得它和字符串 Y 相等，那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的，那它们也是相似的。
 *
 * 例如，"tars" 和 "rats" 是相似的 (交换 0 与 2 的位置)； "rats" 和 "arts" 也是相似的，但是 "star" 不与 "tars"，"rats"，或 "arts" 相似。
 *
 * 总之，它们通过相似性形成了两个关联组：{"tars", "rats", "arts"} 和 {"star"}。注意，"tars" 和 "arts" 是在同一组中，即使它们并不相似。形式上，对每个组而言，要确定一个单词在组中，只需要这个词和该组中至少一个单词相似。
 *
 * 给你一个字符串列表 strs。列表中的每个字符串都是 strs 中其它所有字符串的一个字母异位词。请问 strs 中有多少个相似字符串组？
 *
 *
 *
 * 示例 1：
 *
 * 输入：strs = ["tars","rats","arts","star"]
 * 输出：2
 * 示例 2：
 *
 * 输入：strs = ["omv","ovm"]
 * 输出：1
 *
 *
 * 提示：
 *
 * 1 <= strs.length <= 100
 * 1 <= strs[i].length <= 1000
 * sum(strs[i].length) <= 2 * 104
 * strs[i] 只包含小写字母。
 * strs 中的所有单词都具有相同的长度，且是彼此的字母异位词。
 *
 *
 * 备注：
 *
 *       字母异位词（anagram），一种把某个字符串的字母的位置（顺序）加以改换所形成的新词。
 * @author SHM
 */
public class NumSimilarGroups {
    /**
     * 方法一：并查集
     * 思路及解法
     *
     * 我们把每一个字符串看作点，字符串之间是否相似看作边，那么可以发现本题询问的是给定的图中有多少连通分量。于是可以想到使用并查集维护节点间的连通性。
     *
     * 我们枚举给定序列中的任意一对字符串，检查其是否具有相似性，如果相似，那么我们就将这对字符串相连。
     *
     * 在实际代码中，我们可以首先判断当前这对字符串是否已经连通，如果没有连通，我们再检查它们是否具有相似性，可以优化一定的时间复杂度的常数。
     *
     * 复杂度分析
     *
     * 时间复杂度：O(n^2m + n \log n))O(n
     * 2
     *  m+nlogn))，其中 nn 是字符串的数量。我们需要 O(n^2)O(n
     * 2
     *  ) 地枚举任意一对字符串之间的关系，对于任意一对字符串，我们需要 O(m)O(m) 的时间检查字符串是否相同。在最坏情况下我们需要对并查集执行 O(n)O(n) 次合并，合并的均摊时间复杂度 O(\log n)O(logn)。综上，总的时间复杂度为 O(n^2m + n \log n))O(n
     * 2
     *  m+nlogn))
     *
     * 空间复杂度：O(n)O(n)，其中 nn 是字符串的数量。并查集需要 O(n)O(n) 的空间。
     *
     * 作者：LeetCode-Solution
     * 链接：https://leetcode-cn.com/problems/similar-string-groups/solution/xiang-si-zi-fu-chuan-zu-by-leetcode-solu-8jt9/
     * @param strs
     * @return
     */
    public int numSimilarGroups(String[] strs) {
        int n = strs.length;
        int m = strs[0].length();

        UnionFind uf = new UnionFind(n);
        for (int i = 0; i < n-1; i++) {
            for (int j = i+1; j < n; j++) {
                if(uf.find(i)==uf.find(j)){
                    continue;
                }
                if(isSimilar(strs,i,j)){
                    uf.union(i,j);
                }
            }
        }

        return uf.getCount();
    }

    /**
     * 相似则有且只有二个位置的元素不同
     * @param strs
     * @param x
     * @param y
     * @return
     */
    boolean isSimilar(String[] strs,int x,int y){
        int n = strs[0].length();
        int num = 0;
        for (int i = 0; i < n; i++) {
            if(strs[x].charAt(i)!=strs[y].charAt(i)){
                num++;
            }
            if(num>2){
                return false;
            }
        }
        return true;
    }

    class UnionFind{
        int[] parent;
        int count;
        UnionFind(int n){
            parent = new int[n];
            count = n;
            for (int i = 0; i < n; i++) {
                parent[i] = i;
            }
        }

        int find(int x){
            if(parent[x]!=x){
                parent[x] = find(parent[x]);
            }
            return parent[x];
        }

        void union(int x, int y){
            int newX = find(x);
            int newY = find(y);
            if(newX==newY){
                return;
            }
            parent[newY] = newX;
            count--;
        }

        int getCount(){
            return count;
        }
    }
}
